package com.atguigu.gmall.order.biz.impl;

import java.lang.ref.Reference;
import java.util.Date;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gmall.cart.entity.CartInfo;
import com.atguigu.gmall.common.config.mq.MqService;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.UserAuthUtil;
import com.atguigu.gmall.enums.OrderStatus;
import com.atguigu.gmall.enums.PaymentWay;
import com.atguigu.gmall.enums.ProcessStatus;
import com.atguigu.gmall.feign.cart.CartFeignClient;
import com.atguigu.gmall.feign.product.ProductSkuDetailFeignClient;
import com.atguigu.gmall.feign.user.UserFeignClient;
import com.atguigu.gmall.feign.ware.WareFeignClient;
import com.atguigu.gmall.mq.logistic.OrderLogisticMsg;
import com.atguigu.gmall.mq.ware.WareStockResultMsg;
import com.atguigu.gmall.order.entity.OrderDetail;
import com.atguigu.gmall.order.entity.OrderInfo;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.order.vo.OrderSplitResp;
import com.atguigu.gmall.order.vo.OrderSubmitVo;
import com.atguigu.gmall.order.vo.SkuWare;
import com.atguigu.gmall.user.entity.UserAddress;
import com.google.common.collect.Lists;

import com.atguigu.gmall.order.biz.OrderBizService;
import com.atguigu.gmall.order.vo.OrderConfirmRespVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

//业务逻辑
@Slf4j
@Service
public class OrderBizServiceImpl implements OrderBizService {

    @Autowired
    CartFeignClient cartFeignClient;
    @Autowired
    ProductSkuDetailFeignClient productSkuDetailFeignClient;
    @Autowired
    UserFeignClient userFeignClient;
    @Autowired
    WareFeignClient wareFeignClient;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    OrderInfoService orderInfoService;
    @Autowired
    OrderDetailService orderDetailService;
    @Autowired
    MqService mqService;

    //获取订单确认数据
    @Override
    public OrderConfirmRespVo orderConfirmData() {
        //
        OrderConfirmRespVo vo = new OrderConfirmRespVo();

        //1 远程调用  购物车,获取选中的商品
        //远程调用的时候，要考虑丢失请求头问题，开启feign拦截器
        List<CartInfo> cartInfoList = cartFeignClient.getChecked().getData();
        //CartInfo => SkuDetail
        List<OrderConfirmRespVo.SkuDetail> skuDetailList = cartInfoList.stream()
                .map((item) -> {
                    OrderConfirmRespVo.SkuDetail skuDetail = new OrderConfirmRespVo.SkuDetail();
                    skuDetail.setSkuId(item.getSkuId());
                    skuDetail.setImgUrl(item.getImgUrl());
                    skuDetail.setSkuName(item.getSkuName());
                    skuDetail.setSkuNum(item.getSkuNum());
                    //查询商品库存 远程调用
                    String hasStock = wareFeignClient.hasStock(item.getSkuId(), item.getSkuNum());
                    skuDetail.setHasStock(hasStock);
                    //远程调用 获取实时价格
                    BigDecimal price = productSkuDetailFeignClient.getPrice(item.getSkuId()).getData();
                    skuDetail.setOrderPrice(price);
                    return skuDetail;
                })
                .collect(Collectors.toList());
        vo.setDetailArrayList(skuDetailList);//商品列表

        //2 商品总数量
        Integer totalNum = skuDetailList.stream()
                .map(item -> item.getSkuNum())
                .reduce((o1, o2) -> o1 + o2)
                .get();
        vo.setTotalNum(totalNum);

        //3 订单总金额
        BigDecimal totalAmount = skuDetailList.stream()
                .map((item) -> item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())))
                .reduce((o1, o2) -> o1.add(o2))
                .get();
        vo.setTotalAmount(totalAmount);

        //4 收货地址
        //userId在请求头
        Long userId = UserAuthUtil.getUserId();
        List<UserAddress> userAddressList = userFeignClient.getUserAddress(userId).getData();
        vo.setUserAddressList(userAddressList);

        //5 流水号
        //采用删除对比令牌 的方式
        String tradeNo = "ATGUIGU-" + System.currentTimeMillis() + "-" + userId;
        vo.setTradeNo(tradeNo);//给客户端放一个流水号
        //给redis放一个流水号
        redisTemplate.opsForValue().set(RedisConst.REPEAT_TOKEN + tradeNo, "1", 5, TimeUnit.MINUTES);

        return vo;
    }

    //提交订单
    @Override
    public Long submitOrder(String tradeNo, OrderSubmitVo orderSubmitVo) {

        //前端传来的参数 都需要进行 校验
        //0 参数校验 OrderSubmitVo @Valid @NotEmpty

        //1 校验令牌 token  用删除对比令牌 的方式
        Boolean delete = redisTemplate.delete(RedisConst.REPEAT_TOKEN + tradeNo);
        if (!delete) {
            throw new GmallException(ResultCodeEnum.REPEAT_REQUEST);
        }

        //2 校验库存
        //想要购买的多少件
        List<OrderSubmitVo.OrderDetailListDTO> noStockSku = orderSubmitVo.getOrderDetailList().stream()
                .filter((item) -> "0".equals(wareFeignClient.hasStock(item.getSkuId(), item.getSkuNum())))//远程调用 查询库存 ，过滤出无库存的商品
                .collect(Collectors.toList());
        if (noStockSku != null && noStockSku.size() > 0) {//没有库存的商品
            //获取商品name
            String skuNames = noStockSku.stream()
                    .map((item) -> item.getSkuName())
                    .reduce((o1, o2) -> o1 + ";" + o2)
                    .get();
            //抛出异常，给前端说哪些商品没有库存
            GmallException gmallException = new GmallException(skuNames + ": 没有库存!!", ResultCodeEnum.NO_STOCK.getCode());
            throw gmallException;
        }

        //3 校验价格
        //查询出 价格发生改变的商品
        List<OrderSubmitVo.OrderDetailListDTO> priceChangeSku = orderSubmitVo.getOrderDetailList().stream()
                .filter((item) -> {
                    BigDecimal orderPrice = item.getOrderPrice();//加入购物车的价格
                    BigDecimal price = productSkuDetailFeignClient.getPrice(item.getSkuId()).getData();//商品的实时价格
                    return Math.abs(orderPrice.subtract(price).doubleValue()) >= 0.0001;
                })
                .collect(Collectors.toList());
        if (priceChangeSku != null && priceChangeSku.size() > 0) {//价格发生改变的商品
            String skuNames = priceChangeSku.stream()
                    .map((item) -> item.getSkuName())
                    .reduce((o1, o2) -> o1 + ";" + o2)
                    .get();
            //抛出异常，给前端说哪些商品价格发生改变
            GmallException gmallException = new GmallException(skuNames + ": 价格发生改变!!请重新刷新页面!!", ResultCodeEnum.PRICE_CHANGE.getCode());
            throw gmallException;
        }

        //4 保存订单信息，操作数据库 order_info  根据前端传来的 OrderSubmitVo ==转成==> OrderInfo
        OrderInfo orderInfo = prepareOrderInfo(tradeNo, orderSubmitVo);
        orderInfoService.save(orderInfo);
        // 利用雪花算法生成的id
        Long orderId = orderInfo.getId();

        //5 保存订单详情信息，操作数据库 order_detail
        List<OrderDetail> orderDetailList = prepareOrderDetails(orderSubmitVo, orderInfo);
        orderDetailService.saveBatch(orderDetailList);

        //todo 发送订单创建成功的消息
        mqService.send(orderInfo, MqConst.ORDER_EVENT_EXCHANGE, MqConst.ORDER_CREATE_RK);

        //6 删除购物车中选中的商品
        cartFeignClient.deleteChecked();
        return orderId;
    }

    //准备订单详情信息  order_detail
    private List<OrderDetail> prepareOrderDetails(OrderSubmitVo orderSubmitVo, OrderInfo orderInfo) {
        return orderSubmitVo.getOrderDetailList().stream()
                .map((item) -> {
                    OrderDetail orderDetail = new OrderDetail();
                    orderDetail.setOrderId(orderInfo.getId());
                    orderDetail.setUserId(orderInfo.getUserId());
                    orderDetail.setSkuId(item.getSkuId());
                    orderDetail.setSkuName(item.getSkuName());
                    orderDetail.setImgUrl(item.getImgUrl());
                    orderDetail.setOrderPrice(item.getOrderPrice());
                    orderDetail.setSkuNum(item.getSkuNum());
                    orderDetail.setCreateTime(new Date());
                    //实际支付金额
                    orderDetail.setSplitTotalAmount(item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())));
                    orderDetail.setSplitActivityAmount(new BigDecimal("0"));
                    orderDetail.setSplitCouponAmount(new BigDecimal("0"));
                    return orderDetail;
                })
                .collect(Collectors.toList());
    }

    //准备订单信息 order_info
    private OrderInfo prepareOrderInfo(String tradeNo, OrderSubmitVo orderSubmitVo) {
        OrderInfo orderInfo = new OrderInfo();

        orderInfo.setConsignee(orderSubmitVo.getConsignee());
        orderInfo.setConsigneeTel(orderSubmitVo.getConsigneeTel());
        orderInfo.setDeliveryAddress(orderSubmitVo.getDeliveryAddress());
        orderInfo.setOrderComment(orderSubmitVo.getOrderComment());
        //订单总金额 = 原价金额 - 优惠金额
        BigDecimal totalAmount = orderSubmitVo.getOrderDetailList().stream()
                .map((item) -> item.getOrderPrice().multiply(new BigDecimal(item.getSkuNum())))
                .reduce((o1, o2) -> o1.add(o2))
                .get();
        orderInfo.setTotalAmount(totalAmount);
        //订单状态
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //用户ID
        Long userId = UserAuthUtil.getUserId();
        orderInfo.setUserId(userId);
        //支付方式
        orderInfo.setPaymentWay(PaymentWay.ONLINE.name());

        //订单交易编号（第三方支付用）
        orderInfo.setOutTradeNo(tradeNo);
        //订单描述(第三方支付用)
        String skuName = orderSubmitVo.getOrderDetailList().get(0).getSkuName();
        orderInfo.setTradeBody(skuName);
        orderInfo.setCreateTime(new Date());

        //todo 失效时间  30min不支付，订单就要被关闭
        Date date = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
        orderInfo.setExpireTime(date);
        //进度状态
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        //物流单编号
        orderInfo.setTrackingNo("");
        //父订单编号
        orderInfo.setParentOrderId(null);
        //商品图片路径
        String imgUrl = orderSubmitVo.getOrderDetailList().get(0).getImgUrl();
        orderInfo.setImgUrl(imgUrl);
        orderInfo.setProvinceId(0L);//省id
        orderInfo.setOperateTime(new Date());//操作时间

        //优惠
        orderInfo.setActivityReduceAmount(new BigDecimal("0"));
        orderInfo.setCouponAmount(new BigDecimal("0"));
        orderInfo.setOriginalTotalAmount(totalAmount);
        orderInfo.setFeightFee(new BigDecimal("0"));
        //可退款日期（签收后30天）
        orderInfo.setRefundableTime(new Date());

        return orderInfo;
    }

    @Override
    public void closeOrder(Long id, Long userId) {
        //只有未支付的订单 需要关闭
        // process_status=closed order_status=closed
        ProcessStatus closed = ProcessStatus.CLOSED;
        //更新 数据库 订单状态
        //从死信队列里取的消息，所以能关单的都是超过30min的订单消息，且此操作是幂等性的
        boolean update = orderInfoService.lambdaUpdate()
                .set(OrderInfo::getOrderStatus, closed.getOrderStatus().name())
                .set(OrderInfo::getProcessStatus, closed.getOrderStatus().name())
                .eq(OrderInfo::getId, id)
                .eq(OrderInfo::getUserId, userId)
                .eq(OrderInfo::getOrderStatus, OrderStatus.UNPAID)
                .eq(OrderInfo::getProcessStatus, ProcessStatus.UNPAID)
                .update();
        log.info("订单：{}，关闭：{}", id, update);
    }

    @Override
    public void payedOrder(String out_trade_no, Long userId) {
        //关单消息 和 支付消息 同时抵达，无论谁先执行，最终结果都应该以支付状态为准
        //关单先运行，改为已关闭，支付后运行就改为已支付
        //支付先运行，改为已支付，关单后运行就什么都不做

        // update OrderStatus = 已支付 and ProcessStatus = 已支付 where out_trade_no=? and userId=?
        //  OrderStatus IN(未支付，已关闭) and ProcessStatus IN (未支付、已关闭)
        ProcessStatus payed = ProcessStatus.PAID;
        //修改订单状态 为已支付
        boolean update = orderInfoService.lambdaUpdate()
                .set(OrderInfo::getOrderStatus, payed.getOrderStatus().name())
                .set(OrderInfo::getProcessStatus, payed.name())
                .eq(OrderInfo::getUserId, userId)
                .eq(OrderInfo::getOutTradeNo, out_trade_no)
                .in(OrderInfo::getOrderStatus, OrderStatus.UNPAID.name(), OrderStatus.CLOSED.name())
                .in(OrderInfo::getProcessStatus, ProcessStatus.UNPAID.name(), ProcessStatus.CLOSED.name())
                .update();
        log.info("修改{}订单为已支付状态：{}", out_trade_no, update);
    }

    //修改订单状态
    @Override
    public void updateOrderStockStatus(WareStockResultMsg resultMsg) {
        //订单修改的状态
        ProcessStatus status = ProcessStatus.WAITING_DELEVER;
        switch (resultMsg.getStatus()) {
            case "DEDUCTED": //已减库存==>等待发货
                status = ProcessStatus.WAITING_DELEVER;
                break;
            case "OUT_OF_STOCK": //库存超卖==>等待调货
                status = ProcessStatus.STOCK_EXCEPTION;
                break;
        }
        //获取订单
        OrderInfo orderInfo = orderInfoService.getById(resultMsg.getOrderId());

        //注意：一旦使用消息队列，就和http没有任何关系了，我们以前透传的所有东西都不能用；
        //如果想要后来用的字段，发消息的时候就必须带上。
        //修改订单状态
        orderInfoService.lambdaUpdate()
                .set(OrderInfo::getOrderStatus, status.getOrderStatus().name())
                .set(OrderInfo::getProcessStatus, status.name())
                .eq(OrderInfo::getId, orderInfo.getId())
                .eq(OrderInfo::getUserId, orderInfo.getUserId())
                .eq(OrderInfo::getOrderStatus, OrderStatus.PAID.name())
                .eq(OrderInfo::getProcessStatus, ProcessStatus.PAID.name())
                .update();
        log.info("订单库存状态更新完成");

        //下电子面单，进行发货
        if ("DEDUCTED".equals(resultMsg.getStatus())){
            OrderLogisticMsg logisticMsg = new OrderLogisticMsg();
            logisticMsg.setOrderId(orderInfo.getId());
            logisticMsg.setUserId(orderInfo.getUserId());

            //给等待物流配送的订单队列 发送消息
            mqService.send(logisticMsg, MqConst.ORDER_EVENT_EXCHANGE, MqConst.ORDER_LOGISTIC_RK);
        }
    }

    @Override
    public List<OrderSplitResp> orderSplit(Long orderId, String json) {
        // json = [{"wareId":"1","skuIds":["49"]},{"wareId":"2","skuIds":["50"]}]
        //大订单拆分成 子订单（根据大订单中所有商品的库存分布，拆分成子订单，把子订单存到数据库）
        //获取父订单
        OrderInfo parentOrder = orderInfoService.getById(orderId);

        //获取大订单中所有商品
        List<OrderDetail> orderDetails = orderDetailService.lambdaQuery()
                .eq(OrderDetail::getOrderId, parentOrder.getId())
                .eq(OrderDetail::getUserId, parentOrder.getUserId())
                .list();

        //1 得到大订单中所有商品的库存分布 json ==精确转==> List<SkuWare>>
        List<SkuWare> skuWares = JSON.parseObject(json, new TypeReference<List<SkuWare>>() {
        });
        //2 拆分子订单 childOrders
        AtomicInteger i = new AtomicInteger();
        List<OrderInfo> childOrders = skuWares.stream()
                .map(item -> {
                    OrderInfo childOrder = new OrderInfo();
                    childOrder.setConsignee(parentOrder.getConsignee());
                    childOrder.setConsigneeTel(parentOrder.getConsigneeTel());

                    //当前子订单负责的所有商品
                    List<Long> skuIds = item.getSkuIds();
                    //过滤大订单的商品，如果在当前子订单负责的列表中，过滤出来
                    List<OrderDetail> childDetails = orderDetails.stream()
                            .filter(obj -> skuIds.contains(obj.getSkuId()))
                            .collect(Collectors.toList());
                    //子订单负责的商品
                    childOrder.setOrderDetails(childDetails);
                    //子订单总金额
                    BigDecimal totalAmount = childDetails.stream()
                            .map(o1 -> o1.getOrderPrice().multiply(new BigDecimal(o1.getSkuNum())))
                            .reduce((o1, o2) -> o1.add(o2))
                            .get();
                    childOrder.setTotalAmount(totalAmount);

                    childOrder.setOrderStatus(parentOrder.getOrderStatus());
                    childOrder.setUserId(parentOrder.getUserId());
                    childOrder.setPaymentWay(parentOrder.getPaymentWay());
                    childOrder.setDeliveryAddress(parentOrder.getDeliveryAddress());
                    childOrder.setOrderComment(parentOrder.getOrderComment());
                    childOrder.setOutTradeNo(i.getAndIncrement() + "_" + parentOrder.getOutTradeNo());
                    //商品描述
                    childOrder.setTradeBody(orderDetails.get(0).getSkuName());
                    childOrder.setCreateTime(new Date());
                    childOrder.setExpireTime(parentOrder.getExpireTime());
                    childOrder.setProcessStatus(parentOrder.getProcessStatus());
                    childOrder.setTrackingNo("");
                    childOrder.setParentOrderId(parentOrder.getId());
                    childOrder.setImgUrl(orderDetails.get(0).getImgUrl());

                    //childOrder.setProvinceId(0L);
                    childOrder.setOperateTime(new Date());
                    childOrder.setActivityReduceAmount(new BigDecimal("0"));
                    childOrder.setCouponAmount(new BigDecimal("0"));
                    childOrder.setOriginalTotalAmount(totalAmount);
                    childOrder.setFeightFee(new BigDecimal("0"));
                    //childOrder.setOrderDetails(Lists.newArrayList());
                    childOrder.setWareId(item.getWareId());
                    //childOrder.setRefundableTime(new Date());

                    return childOrder;
                })
                .collect(Collectors.toList());
        //3 保存子订单
        for (OrderInfo orderInfo : childOrders) {
            orderInfoService.save(orderInfo);
            //保存子订单的明细
            Long id = orderInfo.getId();
            List<OrderDetail> details = orderInfo.getOrderDetails().stream()
                    .map(item -> {
                        item.setOrderId(id);//子订单的id进行回填
                        return item;
                    })
                    .collect(Collectors.toList());
            orderDetailService.saveBatch(details);
        }
        //4 修改父订单的状态为 已拆分
        boolean update = orderInfoService.lambdaUpdate()
                .set(OrderInfo::getOrderStatus, OrderStatus.SPLIT.name())
                .set(OrderInfo::getProcessStatus, ProcessStatus.SPLIT.name())
                .eq(OrderInfo::getId, parentOrder.getId())
                .eq(OrderInfo::getUserId, parentOrder.getUserId())
                .update();
        List<Long> ids = childOrders.stream().map(item -> item.getId()).collect(Collectors.toList());
        log.info("拆单完成，大订单：{}拆分为：{}", parentOrder.getId(), ids);
        //5 准备响应结果 List<OrderSplitResp>
        List<OrderSplitResp> collect = childOrders.stream()
                .map(item -> {
                    OrderSplitResp resp = new OrderSplitResp();
                    resp.setOrderId(item.getId());
                    resp.setUserId(item.getUserId());
                    resp.setConsignee(item.getConsignee());
                    resp.setConsigneeTel(item.getConsigneeTel());
                    resp.setOrderComment(item.getOrderComment());
                    resp.setOrderBody(item.getTradeBody());
                    resp.setDeliveryAddress(item.getDeliveryAddress());
                    resp.setPaymentWay("2");
                    resp.setWareId(item.getWareId());

                    //订单详情
                    List<OrderDetail> details = item.getOrderDetails();
                    List<OrderSplitResp.Sku> skuList = details.stream()
                            .map(o1 -> {
                                OrderSplitResp.Sku sku = new OrderSplitResp.Sku();
                                sku.setSkuId(o1.getSkuId());
                                sku.setSkuNum(o1.getSkuNum());
                                sku.setSkuName(o1.getSkuName());
                                return sku;
                            }).collect(Collectors.toList());
                    resp.setDetails(skuList);

                    return resp;
                }).collect(Collectors.toList());

        return collect;
    }

    @Override
    public Long saveSeckillOrder(OrderInfo orderInfo) {
        //保存订单
        boolean save = orderInfoService.save(orderInfo);
        Long id = orderInfo.getId();
        //保存订单明细
        List<OrderDetail> orderDetails = orderInfo.getOrderDetails()
                .stream()
                .map(item -> {
                    item.setOrderId(id);
                    return item;
                }).collect(Collectors.toList());
        orderDetailService.saveBatch(orderDetails);

        //todo 独立设计一套MQ队列交换机

        return id;
    }
}
